#! /bin/bash

. $LSB_DIR/tool-functions || \
	{ echo "invalid lsb dir: $LSB_DIR" >&2; exit 1; }

. $INITRAMFS_DIR_DATA/functions || error_exit "import functions fail"

set_level "${INITRAMFS_PRINT_LEVEL:-INFO}"
[ -n "$INITRAMFS_LOG" ] && set_log "$INITRAMFS_LOG"
msg collect modules

load_conf

kversion=$INITRAMFS_KERNEL_VERSION
fwdbase=${INITRAMFS_DIR_FIRMWARE:-/}
fwd1=$fwdbase/lib/firmware
fwd2=$fwd1/$kversion
mdbase=${INITRAMFS_DIR_MODULE:-/}
md=$mdbase/lib/modules/$INITRAMFS_KERNEL_VERSION
dest=$INITRAMFS_DIR_DEST
[ -n "$kversion" ] || error_exit "could not get kernel version"
[ -d "$fwd1" ] || warn "invalid firmware directory $fwd1"
[ -d "$md" ] || error_exit "invalid module directory or kernel version: $md"
[ -d "$dest" ] || error_exit "invalid dest directory: $dest"
[ -r "$md/modules.dep" ] || { \
	error "could not read $md/modules.dep (run depmod properly)"
	error "depmod: depmod [-b INITRAMFS_DIR_MODULE] [kernel-version]"
	exit 1
}
mpd=${MODPROBE_D:-/etc/modprobe.d}
[ -d "$mpd" ] || mpd=""

# usage: add_firmware_by_one_module
add_firmware_for_one_module()
{
	local fw dfwd1=$dest/lib/firmware dfwd2=$dfwd1/$kversion

	[ -n "$1" ] || error_exit "invalid arguments for adding firmware"
	debug add firmware of module $1

	for fw in $(modinfo -F firmware "$1" 2>/dev/null); do
		[ -d "$dfwd1" ] || mkdir -p "$dfwd1"
		if [ -e "$fwd1/$fw" ]; then
			cp -au "$fwd1/$fw" "$dfwd1"
		elif [ -e "$fwd2/$fw/" ]; then
			[ -d "$dfwd2" ] || mkdir -p "$dfwd2"
			cp -au "$fwd2/$fw" "$dfwd2"
		else
			warn "could not find firmware $fw, ignore."
		fi
	done
}

# usage: add_modprobe_d_by_one_module
add_modprobe_d_for_one_module()
{
	local c dmp="$dest/etc/modprobe.d" m=$1

	m=${1##*/}; m=${m%.ko}
	[ -n "$m" ] || error_exit "invalid module format $1"

	[ -d "$mpd" ] || return 0

	for c in $(ls $mpd/*.conf 2>/dev/null); do
		[ -n "$(grep "options *$m " $c 2>/dev/null)" ] || continue
		[ -d "$dmp" ] || mkdir -p "$dmp"
		verbose "add modprobe.d $c"
		cp "$c" "$dmp"
	done
}

# add_one_module <modname>
add_one_module()
{
	local m mod=$1

	verbose "add one module $mod"
	mod=${mod%.ko}; mod=${mod##*/}
	[ -n "$mod" ] || error_exit "invalid module name $1"

	while read m; do
		local pn dn bn
		pn=${m#$mdbase}
		dn=${pn%/*}
		bn=${pn##*/}; bn=${bn%.ko}
		[ -n "$pn" ] || {
			warn "could not get module $mod, ignore"
			continue
		}
		if [ -d "$mpd" ] && \
		   [ -n "$(grep "blacklist *$bn" $mpd/*.conf 2>/dev/null)" ];
		then
			# blacklist ? ignore it.
			warn "module $pn is a blacklist one, ignore."\
			     "check $mpd/*.conf for details"
			continue
		fi
		debug add module to dir $dest/$dn
		mkdir -p $dest/$dn
		cp -au "$m" "$dest/$dn"
		add_firmware_for_one_module "$m"
		# add softdep ? !! FIXME
		[ -d "$mpd" ] && add_modprobe_d_for_one_module "$bn"
	done <<EOF
$(modprobe -iqS$kversion -d "$mdbase" --show-depends "$mod" | \
		sed 's/^insmod //g; s/ [a-zA-Z_]*=.*//')
EOF
}

# usage: add_modules <modname>...
add_modules()
{
	local x m

	for x in $@; do
		verbose "add module $x"
		m=${x%.ko}; m=${m##*/}
		[ -n "$m" ] || { warn "invalid module $x"; continue; }
		add_one_module "$m"
	done

	return 0
}

# usage: add_sys_path "<sys-pathname>"
add_sys_path()
{
	local path p

	for path in "$@"; do
		verbose "add modules: add path $path"
		p=$path
		while [ -d "$p" ]; do
			[ -r "$p/modalias" ] && \
				add_modules $(cat "$p/modalias")
			p=${p%/*}
		done
	done
}

dep_add_modules()
{
	local root fs bdev

	[ -d /sys/devices ] || error_exit "modules dep required sysfs on /sys"
	[ -r /proc/mounts ] || error_exit "modules dep required proc on /proc"
	eval $(cat /proc/mounts | grep -v "rootfs / rootfs" | grep ' / ' | \
		awk '{print "root="$1" fs="$3}')
	root=$(readlink -f "$root") || error_exit "readlink root fail"
	[ -z "$fs" ] && error_exit "could not decide filesystem"
	[ -z "$fs" ] && { warn "get fs fail, set to ext3"; fs=ext3; }
	verbose "add modules for filesystem $fs"
	add_modules "$fs"

	# block device:
	bdev=${root##*/}
	bdev=${bdev%%[0-9]*}
	[ -d /sys/block/"$bdev" ] || error_exit "find sys block $bdev fail"
	bdev=$(readlink -f /sys/block/"$bdev"/device) || \
		error_exit "readlink sys block $bdev fail"
	add_sys_path "$bdev"
}

hw_add_modules()
{
	error_exit "unimplemented ..."
	:
}

case "$MODULES" in
	dep)
		dep_add_modules
		;;
	hw)
		hw_add_modules
		;;
	*)
		warn "could not find config MODULES, using dep"
		dep_add_modules
		;;
esac

add_modules "$INITRAMFS_EXTRA_MODULES"
[ -f "$md/modules.builtin" ] && \
	cp -au "$md/modules.builtin" "$dest/lib/modules/$kversion/"
[ -f "$md/modules.order" ] && \
	cp -au "$md/modules.order" "$dest/lib/modules/$kversion/"

verbose "DEPMOD $kversion for initramfs"
depmod -a -b"$dest" "$kversion"
rm -f $dest/lib/modules/$kversion/modules.*map

copy_exec /sbin/modprobe /sbin/rmmod

exit 0
